package com.sunday.common.statemachine.study.e6_Fork;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.state.State;

/**
 * Fork 状态的作用和说明
 * fork 状态的作用是将状态流从一个状态分叉（Fork）到多个并行的状态。简单来说，fork 是一种用于状态机并行处理的机制，它允许一个状态流被分开到多个独立的并行路径上，允许状态机在同一时刻处于多个状态。
 * <p>
 * fork 状态的使用场景
 * 并行处理：假设在某个状态下，你需要并行处理多个子任务，fork 就是用来分割处理流向多个子状态的。
 * 层次结构和并行性：在有复杂层次结构的状态机中，fork 可以用于将控制流分发到多个子状态区域，并行执行多个子任务，通常和状态层次结构（如父子状态）结合使用。
 * fork 和 join 的配合
 * fork 通常与 join 配合使用。fork 用于分叉状态流，而 join 用于将这些并行的状态流合并（即，完成所有并行的任务后，继续到下一个状态）。在分叉后，状态机会并行执行多个目标状态，直到所有并行状态都完成，再继续到下一个步骤。
 * <p>
 * fork 的关键特性
 * 目标状态要求是超级状态或立即状态：fork 的目标状态必须是一个超级状态（即父状态），或者是一个可以并行执行的独立状态区域。
 * 并行状态的执行：分叉后，多个目标状态会同时开始，直到它们各自完成后，状态机会继续执行后续步骤。
 * <p>
 * 总结：
 * Fork 状态用于在状态机中分发状态流，使得同一时刻可以进入多个状态。这非常适用于并行处理的场景。
 * 状态层次结构和并行执行：fork 将多个状态分发到不同的并行子状态进行处理，直到所有并行任务完成，再回到最终状态。
 */

@Slf4j
@Configuration  // 声明该类为Spring的配置类
@EnableStateMachine  // 启用Spring状态机功能
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {

    @Override
    public void configure(StateMachineConfigurationConfigurer<OrderStates, OrderEvents> config) throws Exception {
        config
                .withConfiguration()
                .autoStartup(true)
                .listener(listener());
    }

    @Override
    public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
        states
                .withStates()
                .initial(OrderStates.S1)
                .fork(OrderStates.S2)
                .state(OrderStates.S3)
                .and()
                .withStates()
                .parent(OrderStates.S3)
                .initial(OrderStates.S2I)
                .state(OrderStates.S21)
                .state(OrderStates.S22)
                .end(OrderStates.S2F)
                .and()
                .withStates()
                .parent(OrderStates.S3)
                .initial(OrderStates.S3I)
                .state(OrderStates.S31)
                .state(OrderStates.S32)
                .end(OrderStates.S3F);
        ;

    }

    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception {
        transitions
                // 新建订单 -> 检查库存 触发条件 检查库存事件
                .withExternal()  // 配置外部状态转换（即不同状态之间的转换）
                .source(OrderStates.S1)
                .target(OrderStates.S2)
                .event(OrderEvents.TS2)
                .action(context -> {
                    log.info("TS2 : {}", context.getStateMachine().getState().getId());
                })
                .and()

                .withFork()
                .source(OrderStates.S2)
                .target(OrderStates.S21)
                .target(OrderStates.S31)
                .and()

                .withExternal()  // 配置外部状态转换（即不同状态之间的转换）
                .source(OrderStates.S21)
                .target(OrderStates.S22)
                .event(OrderEvents.TS22)
                .and()

                .withExternal()  // 配置外部状态转换（即不同状态之间的转换）
                .source(OrderStates.S31)
                .target(OrderStates.S32)
                .event(OrderEvents.TS32)
                .and()

                .withExternal()  // 配置外部状态转换（即不同状态之间的转换）
                .source(OrderStates.S22)
                .target(OrderStates.S2F)
                .event(OrderEvents.TS2F)
                .and()

                .withExternal()  // 配置外部状态转换（即不同状态之间的转换）
                .source(OrderStates.S32)
                .target(OrderStates.S3F)
                .event(OrderEvents.TS3F)
                .and()
        ;
    }

    /**
     * 为什么触发两次 stateChanged()？
     * <p>
     * 因为状态机中存在层次结构，从 UNPAID 到 PAID 是一次状态变更，而从 PAID 自动切换到 PENDING_OUTBOUND 是另一层次变更。
     * 为什么 UNPAID to PAID 出现在 PENDING_OUTBOUND 后？
     * <p>
     * 日志记录的顺序是由状态进入的顺序决定的：先进入 PAID，再进入其子状态 PENDING_OUTBOUND。但 PENDING_OUTBOUND 是最终的细化状态，因此先记录。
     * 如何解决？
     * <p>
     * 在 stateChanged() 方法中通过 getParent() 方法提取父状态，记录完整的父子状态信息。
     * 也可以考虑使用 entry() 和 exit() 钩子方法进行状态进入和退出日志的更精确记录。
     */
    @Bean
    public StateMachineListener<OrderStates, OrderEvents> listener() {
        return new StateMachineListenerAdapter<>() {
            @Override
            public void stateChanged(State<OrderStates, OrderEvents> from, State<OrderStates, OrderEvents> to) {
                log.info("State change from {} to {}", from == null ? "无" : from.getId(), to.getId());
            }
        };
    }
}